Explain coupling and cohesion with examples.
Coupling and Cohesion: Core Concepts in Software Designβ
Coupling and cohesion are fundamental principles in software engineering that significantly influence the quality, maintainability, and reusability of software systems. While they represent different aspects of software design, they are closely related and often considered together when evaluating software architecture.
Cohesionβ
Definitionβ
Cohesion refers to the degree to which the elements within a module belong together. It measures how strongly related the responsibilities of a single module are. A highly cohesive module performs a single, well-defined task or is focused on a single concern.
Levels of Cohesion (from Lowest to Highest)β
- Coincidental Cohesion (Lowest): Elements are grouped arbitrarily with no meaningful relationship.
- Logical Cohesion: Elements perform similar functions but are otherwise unrelated.
- Temporal Cohesion: Elements are related by when they are processed.
- Procedural Cohesion: Elements are grouped because they follow a specific sequence of execution.
- Communicational Cohesion: Elements operate on the same data.
- Sequential Cohesion: Output from one element serves as input to another.
- Functional Cohesion (Highest): All elements contribute to a single, well-defined task.
Examples of Different Cohesion Levelsβ
1. Low Cohesion (Coincidental)β
// A utility class with unrelated operations
public class Utilities {
public static void validateUserInput(String input) { /* ... */ }
public static void connectToDatabase() { /* ... */ }
public static void formatDateTime(Date date) { /* ... */ }
public static void calculateTax(double amount) { /* ... */ }
}
Issues: This class has no clear purpose and contains unrelated methods. It's difficult to name appropriately and likely to grow excessively large.
2. Medium Cohesion (Communicational)β
// A class operating on Order data, but with mixed responsibilities
public class OrderProcessor {
public void validateOrder(Order order) { /* ... */ }
public void calculateOrderTotal(Order order) { /* ... */ }
public void saveOrderToDatabase(Order order) { /* ... */ }
public void generateOrderPDF(Order order) { /* ... */ }
public void emailOrderConfirmation(Order order) { /* ... */ }
}
Issues: While all methods operate on Order objects, they serve different purposes (validation, calculation, persistence, document generation, communication).
3. High Cohesion (Functional)β
// A class focused solely on order validation
public class OrderValidator {
public boolean isOrderValid(Order order) { /* ... */ }
private boolean areProductsInStock(List<Product> products) { /* ... */ }
private boolean isPaymentInformationValid(Payment payment) { /* ... */ }
private boolean isShippingAddressValid(Address address) { /* ... */ }
}
// A class focused solely on payment processing
public class PaymentProcessor {
public void processPayment(Order order) { /* ... */ }
private void authorizeTransaction(Payment payment) { /* ... */ }
private void recordPaymentDetails(Payment payment) { /* ... */ }
}
Benefits: Each class has a single, clear responsibility. Methods within the class work together to achieve a specific task.
Couplingβ
Definitionβ
Coupling measures the degree of interdependence between software modules. It indicates how closely connected two modules are and how much one module knows about the inner workings of another module.
Types of Coupling (from Highest to Lowest)β
- Content Coupling (Highest): One module directly modifies the internal data of another module.
- Common Coupling: Multiple modules share global data.
- Control Coupling: One module controls the flow of another by passing information on what to do.
- Stamp Coupling: Modules share complex data structures, though they might not need all the information.
- Data Coupling: Modules communicate through parameters, passing only the data needed.
- Message Coupling (Lowest): Modules communicate through standardized interfaces without sharing data structures.
Examples of Different Coupling Levelsβ
1. High Coupling (Content Coupling)β
// Class A directly accesses and modifies class B's internal state
public class AccountManager {
public void updateUserBalance(User user, double amount) {
// Directly modifying another class's private field
user.balance += amount; // Assumes balance is not private or accessed via reflection
// Bypassing validation or business rules in the User class
if (user.balance < 0) {
user.balance = 0;
}
}
}
public class User {
public double balance; // Public field allowing direct access
// Other user properties and methods
}
Issues: AccountManager relies on the internal implementation of User, violating encapsulation. Changes to User's internal representation will likely break AccountManager.
2. Medium Coupling (Control Coupling)β
public class OrderProcessor {
public void processOrder(Order order, String action) {
if (action.equals("validate")) {
// Validation logic
} else if (action.equals("save")) {
// Persistence logic
} else if (action.equals("ship")) {
// Shipping logic
}
}
}
// Client code
public class OrderService {
private OrderProcessor processor;
public void validateOrder(Order order) {
processor.processOrder(order, "validate");
}
public void shipOrder(Order order) {
processor.processOrder(order, "ship");
}
}
Issues: OrderService controls the behavior of OrderProcessor through the action parameter, creating a dependency on string values.
3. Low Coupling (Message Coupling)β
// Clear interfaces defining module interactions
public interface OrderValidator {
boolean isValid(Order order);
}
public interface OrderRepository {
void save(Order order);
}
public interface ShippingService {
void ship(Order order);
}
// Implementation classes
public class OrderValidator implements OrderValidator {
@Override
public boolean isValid(Order order) {
// Validation logic
return true;
}
}
// Client code using dependency injection
public class OrderProcessor {
private final OrderValidator validator;
private final OrderRepository repository;
private final ShippingService shippingService;
// Constructor injection
public OrderProcessor(
OrderValidator validator,
OrderRepository repository,
ShippingService shippingService) {
this.validator = validator;
this.repository = repository;
this.shippingService = shippingService;
}
public void processOrder(Order order) {
if (validator.isValid(order)) {
repository.save(order);
shippingService.ship(order);
}
}
}
Benefits: OrderProcessor depends only on interfaces, not concrete implementations. Components communicate through well-defined methods with necessary parameters.
Relationship Between Coupling and Cohesionβ
Coupling and cohesion often have an inverse relationship in well-designed software:
High Cohesion + Low Coupling = Good Design
Low Cohesion + High Coupling = Poor Design
Visual Representationβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β Ideal Zone β
β βββββββββββββ βββββββββββββ β
β β β Low β β β
β β Module A βββββββββββΊ Module B β β
β β β Coupling β β β
β βββββββββββββ βββββββββββββ β
β β
β ββHigh Cohesionββ ββHigh Cohesionββ β
β β - Related β β - Related β β
β β functions β β functions β β
β β - Single β β - Single β β
β β responsibility β responsibility β β
β βββββββββββββββββ βββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Real-World Example: E-commerce Systemβ
Let's examine a practical example of an e-commerce system to demonstrate coupling and cohesion:
Poor Design (Low Cohesion, High Coupling)β
public class ECommerceSystem {
private Database db;
public void registerUser(String username, String password) {
// User registration logic
db.execute("INSERT INTO users VALUES ('" + username + "', '" + password + "')");
}
public void processOrder(int userId, List<Product> products) {
// Order processing logic
double total = 0;
for (Product p : products) {
total += p.price;
}
// Update inventory
for (Product p : products) {
int newQuantity = p.quantity - 1;
db.execute("UPDATE products SET quantity = " + newQuantity + " WHERE id = " + p.id);
}
// Process payment
chargeCreditCard(userId, total);
// Send email
User user = (User) db.execute("SELECT * FROM users WHERE id = " + userId);
sendEmail(user.email, "Order Confirmation", "Your order has been processed. Total: $" + total);
}
private void chargeCreditCard(int userId, double amount) {
// Payment processing logic
CreditCardInfo card = (CreditCardInfo) db.execute("SELECT * FROM credit_cards WHERE user_id = " + userId);
// Process payment with card details
}
private void sendEmail(String to, String subject, String body) {
// Email sending logic
}
}
Issues:
- Low Cohesion: The ECommerceSystem class handles multiple unrelated responsibilities (user management, order processing, payment, email, database operations).
- High Coupling: The class is tightly coupled to the database implementation, directly executing SQL queries.
Better Design (High Cohesion, Low Coupling)β
// User Management
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User registerUser(String username, String password) {
User user = new User(username, password);
return userRepository.save(user);
}
}
// Order Processing
public class OrderService {
private final ProductRepository productRepository;
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
public OrderService(
ProductRepository productRepository,
OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService) {
this.productRepository = productRepository;
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
public Order createOrder(User user, List<Product> products) {
// Create order
Order order = new Order(user, products);
// Calculate total
double total = order.calculateTotal();
// Process payment
PaymentResult result = paymentService.processPayment(user, total);
if (result.isSuccessful()) {
// Update inventory
productRepository.updateInventory(products);
// Save order
orderRepository.save(order);
// Send notification
notificationService.sendOrderConfirmation(user, order);
}
return order;
}
}
// Payment Processing
public class PaymentService {
private final PaymentGateway paymentGateway;
public PaymentService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
public PaymentResult processPayment(User user, double amount) {
return paymentGateway.charge(user.getPaymentInfo(), amount);
}
}
// Notifications
public class EmailNotificationService implements NotificationService {
private final EmailSender emailSender;
public EmailNotificationService(EmailSender emailSender) {
this.emailSender = emailSender;
}
@Override
public void sendOrderConfirmation(User user, Order order) {
String subject = "Order Confirmation";
String body = "Your order #" + order.getId() + " has been processed. Total: $" + order.getTotal();
emailSender.sendEmail(user.getEmail(), subject, body);
}
}
// Data Access Interfaces
public interface UserRepository {
User save(User user);
User findById(int id);
}
public interface ProductRepository {
void updateInventory(List<Product> products);
}
public interface OrderRepository {
Order save(Order order);
}
public interface NotificationService {
void sendOrderConfirmation(User user, Order order);
}
public interface PaymentGateway {
PaymentResult charge(PaymentInfo paymentInfo, double amount);
}
Benefits:
- High Cohesion: Each class has a single, clear responsibility (user management, order processing, payment processing, notifications).
- Low Coupling: Classes communicate through interfaces, hiding implementation details. Dependency injection is used to provide dependencies.
Best Practices to Improve Cohesion and Reduce Couplingβ
For Higher Cohesionβ
- Single Responsibility Principle: Each class should have only one reason to change.
- Extract Classes: Split large classes with multiple responsibilities into smaller, focused classes.
- Organize Code by Feature: Group related functionality together.
- Keep Methods Short and Focused: Methods should do one thing and do it well.
For Lower Couplingβ
- Dependency Inversion: Depend on abstractions, not concrete implementations.
- Use Interfaces: Define clear contracts between components.
- Dependency Injection: Pass dependencies instead of creating them internally.
- Avoid Global State: Minimize use of global variables and singletons.
- Use Events/Observers: Decouple components using event-based communication.
- Law of Demeter: Talk only to immediate friends, not friends of friends.
Conclusionβ
Coupling and cohesion are critical concepts in software design that significantly impact the quality, maintainability, and reusability of software systems:
- High cohesion creates modules that are focused, understandable, and reusable.
- Low coupling produces systems that are flexible, maintainable, and testable.
Together, these principles guide the development of robust software architectures by promoting clear separation of concerns and well-defined component interactions. By striving for high cohesion and low coupling, developers can create modular, adaptable systems that are easier to develop, test, and maintain.